/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.rendering;

import java.util.Map;
import javax.portlet.WindowState;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.apereo.portal.layout.IUserLayoutManager;
import org.apereo.portal.layout.om.IStylesheetDescriptor;
import org.apereo.portal.layout.om.IStylesheetParameterDescriptor;
import org.apereo.portal.portlet.PortletUtils;
import org.apereo.portal.portlet.om.IPortletWindow;
import org.apereo.portal.portlet.registry.IPortletWindowRegistry;
import org.apereo.portal.portlet.rendering.IPortletRenderer;
import org.apereo.portal.url.IPortalRequestInfo;
import org.apereo.portal.url.IUrlSyntaxProvider;
import org.apereo.portal.utils.Tuple;
import org.apereo.portal.utils.cache.CacheKey;
import org.apereo.portal.xml.stream.FilteringXMLEventReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Enforces a specific WindowState based on the "dashboardForcedWindowState" {@link
 * IStylesheetDescriptor} parameter. For renderings where a specific portlet is not specified and
 * the stylesheet descriptor specifies a dashboardForcedWindowState then all portlets in the
 * pipeline will have their window state set to the specified value. If the IStylesheetDescriptor is
 * not specified a check is made to ensure the window states are not {@link WindowState#MAXIMIZED},
 * {@link IPortletRenderer#DETACHED}, or {@link IPortletRenderer#EXCLUSIVE}
 */
public class WindowStateSettingsStAXComponent extends StAXPipelineComponentWrapper {
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private IUrlSyntaxProvider urlSyntaxProvider;
    private IPortletWindowRegistry portletWindowRegistry;
    private StylesheetAttributeSource stylesheetAttributeSource;

    @Autowired
    public void setPortletWindowRegistry(IPortletWindowRegistry portletWindowRegistry) {
        this.portletWindowRegistry = portletWindowRegistry;
    }

    @Autowired
    public void setUrlSyntaxProvider(IUrlSyntaxProvider urlSyntaxProvider) {
        this.urlSyntaxProvider = urlSyntaxProvider;
    }

    public void setStylesheetAttributeSource(StylesheetAttributeSource stylesheetAttributeSource) {
        this.stylesheetAttributeSource = stylesheetAttributeSource;
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.rendering.PipelineComponent#getCacheKey(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public CacheKey getCacheKey(HttpServletRequest request, HttpServletResponse response) {
        // Initiating rendering of portlets will change the stream at all
        return this.wrappedComponent.getCacheKey(request, response);
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.rendering.PipelineComponent#getEventReader(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public PipelineEventReader<XMLEventReader, XMLEvent> getEventReader(
            HttpServletRequest request, HttpServletResponse response) {
        final PipelineEventReader<XMLEventReader, XMLEvent> pipelineEventReader =
                this.wrappedComponent.getEventReader(request, response);

        final XMLEventReader eventReader = pipelineEventReader.getEventReader();

        final IStylesheetDescriptor stylesheetDescriptor =
                stylesheetAttributeSource.getStylesheetDescriptor(request);

        final IPortalRequestInfo portalRequestInfo =
                this.urlSyntaxProvider.getPortalRequestInfo(request);

        final XMLEventReader filteredEventReader;
        if (portalRequestInfo.getTargetedPortletWindowId() == null) {
            final IStylesheetParameterDescriptor defaultWindowStateParam =
                    stylesheetDescriptor.getStylesheetParameterDescriptor(
                            "dashboardForcedWindowState");
            if (defaultWindowStateParam != null) {
                // Set all window states to the specified default
                final WindowState windowState =
                        PortletUtils.getWindowState(defaultWindowStateParam.getDefaultValue());
                filteredEventReader =
                        new SinglePortletWindowStateSettingXMLEventReader(
                                request, eventReader, windowState);
            } else {
                // Make sure there aren't any portlets in a "targeted" window state
                filteredEventReader =
                        new NonTargetedPortletWindowStateSettingXMLEventReader(
                                request, eventReader);
            }
        } else {
            // Not mobile, don't bother filtering
            filteredEventReader = eventReader;
        }

        final Map<String, String> outputProperties = pipelineEventReader.getOutputProperties();
        return new PipelineEventReaderImpl<XMLEventReader, XMLEvent>(
                filteredEventReader, outputProperties);
    }

    private abstract class PortletWindowStateSettingXMLEventReader extends FilteringXMLEventReader {
        private final HttpServletRequest request;

        public PortletWindowStateSettingXMLEventReader(
                HttpServletRequest request, XMLEventReader reader) {
            super(reader);
            this.request = request;
        }

        @Override
        protected final XMLEvent filterEvent(XMLEvent event, boolean peek) {
            if (event.isStartElement()) {
                final StartElement startElement = event.asStartElement();

                final QName name = startElement.getName();
                final String localName = name.getLocalPart();
                if (IUserLayoutManager.CHANNEL.equals(localName)
                        || IUserLayoutManager.CHANNEL_HEADER.equals(localName)) {
                    final Tuple<IPortletWindow, StartElement> portletWindowAndElement =
                            portletWindowRegistry.getPortletWindow(request, startElement);
                    if (portletWindowAndElement == null) {
                        logger.warn("No portlet window found for: {}", localName);
                        return event;
                    }

                    final IPortletWindow portletWindow = portletWindowAndElement.first;

                    final WindowState windowState = getWindowState(portletWindow);

                    // Set the portlet's state, skip if the state is already correct
                    final WindowState currentWindowState = portletWindow.getWindowState();
                    if (!windowState.equals(currentWindowState)) {
                        portletWindow.setWindowState(windowState);

                        if (logger.isDebugEnabled()) {
                            logger.debug(
                                    "Changing WindowState from {} to {} for {}",
                                    new Object[] {currentWindowState, windowState, portletWindow});
                        }

                        portletWindowRegistry.storePortletWindow(request, portletWindow);
                    }

                    return portletWindowAndElement.second;
                }
            }

            return event;
        }

        protected abstract WindowState getWindowState(IPortletWindow portletWindow);
    }

    private class SinglePortletWindowStateSettingXMLEventReader
            extends PortletWindowStateSettingXMLEventReader {
        private final WindowState state;

        public SinglePortletWindowStateSettingXMLEventReader(
                HttpServletRequest request, XMLEventReader reader, WindowState state) {
            super(request, reader);
            this.state = state;
        }

        @Override
        protected WindowState getWindowState(IPortletWindow portletWindow) {
            return state;
        }
    }

    private class NonTargetedPortletWindowStateSettingXMLEventReader
            extends PortletWindowStateSettingXMLEventReader {

        public NonTargetedPortletWindowStateSettingXMLEventReader(
                HttpServletRequest request, XMLEventReader reader) {
            super(request, reader);
        }

        @Override
        protected WindowState getWindowState(IPortletWindow portletWindow) {
            final WindowState windowState = portletWindow.getWindowState();
            if (windowState != null && PortletUtils.isTargetedWindowState(windowState)) {
                return WindowState.NORMAL;
            }
            return windowState;
        }
    }
}
